float4x4 View;
float4x4 InverseView;
float4x4 InverseProjection;

#define NUM_CASCADES 3
float4x4 LightView[NUM_CASCADES];
float4x4 LightProjection[NUM_CASCADES];
float4x4 InverseLightProjection[NUM_CASCADES];
float CascadeDepths[NUM_CASCADES+1];
float CascadeWidth;

float3 LightDirection;
float3 LightColor;

float2 HalfPixel;

bool Shadows;

texture NormalMap;
texture DepthMap;
texture ShadowMap;

sampler shadowSampler = sampler_state
{
    Texture = (ShadowMap);
    AddressU = CLAMP;
    AddressV = CLAMP;
    MagFilter = POINT;
    MinFilter = POINT;
    Mipfilter = POINT;
};

sampler normalSampler = sampler_state
{
    Texture = (NormalMap);
    AddressU = CLAMP;
    AddressV = CLAMP;
    MagFilter = POINT;
    MinFilter = POINT;
    Mipfilter = POINT;
};

sampler depthSampler = sampler_state
{
    Texture = (DepthMap);
    AddressU = CLAMP;
    AddressV = CLAMP;
    MagFilter = POINT;
    MinFilter = POINT;
    Mipfilter = POINT;
};

struct VertexShaderInput
{
    float3 Position : POSITION0;
	float2 TexCoord : TEXCOORD0;
};

struct VertexShaderOutput
{
    float4 Position			: POSITION0;
	float2 ScreenPosition	: TEXCOORD0;
};

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
    VertexShaderOutput output;

	output.Position = float4(input.Position, 1);
    output.ScreenPosition = input.TexCoord - HalfPixel;

    return output;
}

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
    //get normal data from the normalMap
    float4 normalData = tex2D(normalSampler, input.ScreenPosition);

    //tranform normal back into [-1,1] range
    float3 normal = 2.0f * normalData.xyz - 1.0f;

    //surface-to-light vector
	float3 lDir = mul(LightDirection, View);
    float3 lightVector = -normalize(lDir);

    //compute diffuse light
    float NdL = max(0,dot(normal,lightVector));

    clip(NdL);

    //compute view-space position
	float z = tex2D(depthSampler, input.ScreenPosition).r;
	float x = input.ScreenPosition.x * 2.0 - 1.0;
	float y = (1 - input.ScreenPosition.y) * 2.0 - 1.0;
	float4 position = float4(x, y, z, 1.0f);
	position = mul(position, InverseProjection);
	position /= position.w;

	if (Shadows)
	{
		// work out which cascade we're in using viewspace z
        int cascadeIdx = NUM_CASCADES;
		for (int i = 0; i < NUM_CASCADES; ++i)
        {
            if (-position.z > CascadeDepths[i] && -position.z < CascadeDepths[i+1])
                cascadeIdx = i;
        }
        clip(NUM_CASCADES - cascadeIdx - 1);

		// position in world
		float4 positionWS = mul(float4(position.xyz, 1.0), InverseView);

		// position in light space
        float4 realPositionLightVS = mul(float4(positionWS.xyz, 1.0), LightView[cascadeIdx]);

        // position in light screen space
		float4 positionLightSS = mul(float4(realPositionLightVS.xyz, 1.0), LightProjection[cascadeIdx]);
		positionLightSS /= positionLightSS.w;

        // sample the occlusion texture
		float2 lightTexCoord;
		lightTexCoord.x = (CascadeWidth * cascadeIdx) + (0.5 * (positionLightSS.x + 1) * CascadeWidth);
		lightTexCoord.y = 0.5 * (-positionLightSS.y + 1);

        // unproject into the light view space
        float depthFromLight = tex2D(shadowSampler, lightTexCoord).r;
	    positionLightSS.z = depthFromLight;
	    float4 positionAsSeenByLightVS = mul(positionLightSS, InverseLightProjection[cascadeIdx]);
	    positionAsSeenByLightVS /= positionAsSeenByLightVS.w;

        // clip if the position seen by the light is greater (wait what? are these values negative?)
		//return realPositionLightVS.z - positionAsSeenByLightVS.z - 0.1f;
        clip(realPositionLightVS.z - positionAsSeenByLightVS.z + (0.06 * (cascadeIdx + 1)));
	}

    float3 diffuseLight = NdL * LightColor.rgb;

    //output the two lights
    return float4(diffuseLight.rgb, 1.0) ;
}

technique Light
{
    pass Pass1
    {
        VertexShader = compile vs_3_0 VertexShaderFunction();
        PixelShader = compile ps_3_0 PixelShaderFunction();
    }
}
